package data;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import kit.Component;
import tablature.TimeSignature;

/**
 * Class: UserPreferences.java
 * Description: This is a storage place for all of the user's preferences.
 * The most important thing is storing all the configured
 * components, so the user doesn't have to re-configure them
 * when he restarts the program.
 * The components are stored in the Preferences and a separate
 * ArrayList keeps order of them by their assignment number.
 * @author et
 */
public class UserPreferences
{
    // Fields for accessing the Preferences
    private final String MIDI_IN = "MIDI_IN";
    private final String TEMPO = "TEMPO";
    private final String TIME_SIGNATURE = "TIME_SIGNATURE";
    private final String BACKGROUND_METRONOME_SOUND = "BACKGRUOND_METRONOME_SOUND";

    private final String ASSIGNMENTS = "ASSIGNMENTS";
    public static final String NULL = "";

    // A singleton instance of this class.
    private static UserPreferences instance;

    // The list that stores all the assignments in order.
    private ArrayList <Integer> assignments;

    // The actual Preferences object.
    private Preferences prefs;

    

    /**
     * Gets a singleton instance of UserPreferences.
     * @return instance - a singleton instance of this class.
     */
    public static UserPreferences getInstance()
    {
        if (instance == null)
        {
            instance = new UserPreferences();
        }
        return instance;
    }

    /**
     * Constructor for UserPreferences.
     */
    private UserPreferences()
    {
        // The prefs is set to access preferences specifically for this class.
        prefs = Preferences.userNodeForPackage(this.getClass());
        
        // Get the arraylist of assignments that's currently stored,
        // or create a new list.
        try
        {
            assignments = (ArrayList<Integer>) PrefObj.getObject(prefs, ASSIGNMENTS);
        }
        catch (Exception ex)
        {
           assignments  = new ArrayList<Integer>();
        }
    }

    /**
     * Sets the current MIDI in device.
     * @param midiIn - the current MIDI in device.
     */
    public void setMidiIn(String midiIn)
    {
        prefs.put(MIDI_IN, midiIn);
        flush();
    }

    /**
     * Gets the current MIDI in device.
     * @return the current MIDI in device.
     */
    public String getMidiIn()
    {
        return prefs.get(MIDI_IN, NULL);
    }

    /**
     * Sets the preferred tempo.
     * @param tempo - the preferred tempo
     */
    public void setTempo(int tempo)
    {
        prefs.putInt(TEMPO, tempo);
    }

    /**
     * Gets the preferred tempo.
     * @return the preferred tempo.
     */
    public int getTempo()
    {
        return prefs.getInt(TEMPO, 60);
    }

    /**
     * Sets the preferred time signature.
     * @param timeSignature - the preferred time signature.
     */
    public void setTimeSignature(TimeSignature timeSignature)
    {
        try
        {
            PrefObj.putObject(prefs, TIME_SIGNATURE, timeSignature);
        }
        catch (Exception ex)
        {
            System.out.println("Could not set the time signature");
        }
    }

    /**
     * Gets the preferred time signature.
     * @return the preferred time signature or the default one if none specified.
     */
    public TimeSignature getTimeSignature()
    {
        try
        {
            return (TimeSignature) PrefObj.getObject(prefs, TIME_SIGNATURE);
        }
        catch (Exception ex)
        {
            System.out.println("Could not get the preferred time signature");
        }

        return TimeSignature.FOURFOUR;
    }

    /**
     * Sets whether or not the user wants background metronome sound.
     * @param sound - True to have background sound, false otherwise.
     */
    public void setBackgroundMetronomeSound(boolean sound)
    {
        prefs.putBoolean(BACKGROUND_METRONOME_SOUND, sound);
    }

    /**
     * Gets whether the user wants background sound or not.
     * @return whether the user wants background sound.
     */
    public boolean hasBackgroundMetronomeSound()
    {
        return prefs.getBoolean(BACKGROUND_METRONOME_SOUND, false);
    }

    /**
     * Adds a component to be stored.
     * The identifier is the component assignment number.
     * If one already exists, then this will overwrite the old one.
     * @param c - the component to add.
     */
    public void addComponent(Component c)
    {
        int assignment = c.getAssignment();
        try
        {
            // Write this to the system.
            // This will overwrite anything currently with the
            // same identifier.
            PrefObj.putObject(prefs, String.valueOf(assignment), c);

            // If it exists somewhere, overwrite it.
            if (assignments.contains(c.getAssignment()))
            {
                int pos = assignments.indexOf(assignment);
                assignments.set(pos, assignment);
            }
            // Else apend it to the end of the list.
            else
            {
                assignments.add(assignment);
            }
            putArrayList(assignments);

        }
        catch (Exception ex)
        {
            System.out.println("Could not add component.");
            System.err.println(ex);
        }
    }

    
    /**
     * Removes a component from the arraylist.
     * There's no way to remove the component written to the system,
     * but it doesn't matter since the only components considered
     * are the ones in the arraylist.
     * @param c - the component to remove
     */
    public void removeComponent(Component c)
    {
        assignments.remove((Object) c.getAssignment());
        putArrayList(assignments);
    }

    /**
     * Moves a component up one in the arraylist.
     * @param c - the component to move up.
     */
    public void moveComponentUp(Component c)
    {
        int pos = assignments.indexOf(c.getAssignment());
        if (pos - 1 >= 0)
        {
            Collections.swap(assignments, pos, pos -1);
            putArrayList(assignments);
        }
    }

    /**
     * Moves a component down one in the arraylist.
     * @param c - the component to move down.
     */
    public void moveComponentDown(Component c)
    {
        int pos = assignments.indexOf(c.getAssignment());
        if (pos + 1 < assignments.size())
        {
            Collections.swap(assignments, pos, pos +1);
            putArrayList(assignments);
        }
    }

    /**
     * Gets a list of components that can be used.
     * @return components - the list of components that can be used.
     */
    public ArrayList <Component> getComponents()
    {
        ArrayList <Component> components = new ArrayList<Component>();
        for (Integer assignment : assignments)
        {
            Component c = getComponent(assignment);
            components.add(c);
        }

        return components;
    }


    /**
     * Gets a component given an assignment number.
     * @param assignment - the assignment number (the identifier
     * @return c - the component
     */
    public Component getComponent(int assignment)
    {
        Component c = null;
        try
        {
            c = (Component) PrefObj.getObject(prefs, String.valueOf(assignment) );
        }
        catch (Exception ex)
        {
            System.out.println("Could not get component");
            System.out.println(ex);
        }
        return c;
    }

    /**
     * Puts a new arraylist into the system overwriting the old one.
     * @param assignments - the new arraylist.
     */
    private void putArrayList(ArrayList assignments)
    {
        try
        {
            PrefObj.putObject(prefs, ASSIGNMENTS, assignments);
        }
        catch (Exception ex)
        {
            System.out.println("Could not put in the arraylist");
            System.err.println(ex);
        }
        flush();
    }


    /**
     * Clears the preferences and the arraylist.
     * Be careful with this.
     * This is purposely set to private because I haven't found a reason
     * for anything outside the class to use it.
     */
    private void clear()
    {
        try
        {
            prefs.clear();
            assignments  = new ArrayList<Integer>();
            PrefObj.putObject(prefs, ASSIGNMENTS, assignments);
            flush();
        } 
        catch (Exception ex)
        {
            System.out.println("Could not clear preferences.");
            System.err.println(ex);
        }
    }

    /**
     * Flush the preferences.
     */
    private void flush()
    {
        try
        {
            prefs.flush();
        } catch (BackingStoreException ex) {
            System.out.println("Could not flush preferences");
            System.err.println(ex);
        }
    }
}

